// Client.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "Client.h"
#include "ClientUpdate.h"
#include "SelectTutorial.h"
#include "ProgDlg.h"

#include "Debug/Trace.h"
#include "Debug/Assert.h"
#include "Debug/ExceptionHandler.h"
#include "Debug/MemoryGuard.h"
#include "Display/PrimitiveMaterial.h"
#include "Display/PrimitiveSurface.h"

#include "Standard/CommandLine.h" 
#include "Standard/Process.h"
#include "Standard/CharString.h"
#include "Standard/Types.h"
#include "Standard/Settings.h"
#include "Standard/Timer.h"
#include "File/FileDisk.h"
#include "Factory/BrokerFolder.h"
#include "System/Platform.h"
#include "Render3d/NodeParticleSystem.h"
#include "Render3d/NodeStarField.h"
#include "Render3D/NodeBoolean.h"
#include "Game/NounProjectile.h"
#include "Game/Trail.h"
#include "Interface/GameDocument.h"

//#include "SelfUpdate/ClientUpdate.h"

#pragma warning( disable: 4800 )

//----------------------------------------------------------------------------

const char * DEFAULT_INTERFACE = "Interface.prt";

#ifdef _DEBUG
const char * LOBBY_EXE = "GCQLD.exe";
const char * CLIENT_SETUP_EXE = "ClientSetupD.exe";
const char * BUG_REPORT_EXE = "BugReportD.exe DarkSpace";
const char * LOCAL_SERVER_EXE = "LocalServerD.exe";
#else
const char * LOBBY_EXE = "GCQL.exe";
const char * CLIENT_SETUP_EXE = "ClientSetup.exe";
const char * BUG_REPORT_EXE = "BugReport.exe DarkSpace";
const char * LOCAL_SERVER_EXE = "LocalServer.exe";
#endif

//----------------------------------------------------------------------------

#ifdef _DEBUG
//#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CClientApp

BEGIN_MESSAGE_MAP(CClientApp, CWinApp)
	//{{AFX_MSG_MAP(CClientApp)
		// NOTE - the ClassWizard will add and remove mapping macros here.
		//    DO NOT EDIT what you see in these blocks of generated code!
	//}}AFX_MSG
	ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CClientApp construction

CClientApp::CClientApp()
{
	//m_RunLobbyOnExit = true;
	m_SessionId = 0;
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CClientApp object

CClientApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CClientApp initialization

// Main game loop, and code to limit the FPS to reduce CPU usage
void CClientApp::RunGame( InterfaceContext * pInterface )
{
#ifndef _DEBUG
	__try {
#endif
		// THE MAIN GAME LOOP 
		while( pInterface->render() ) {};

#ifndef _DEBUG
	}
	__except( ProcessException( GetExceptionInformation() ) )
	{
		m_ErrorMessage = _T("An unknown error occured while running DarkSpace, please send in bug report!");
		m_Error = true;
	}
#endif
}

int CClientApp::StartGame()
{
#ifdef _DEBUG
	// this stops the massive memory dumps on program exit..
	_CrtSetReportMode( _CRT_WARN, 0 );
#endif

	m_Error = false;
	SetProcessErrorMode( EM_DIALOG );

	// Force client to use CPU 0 only... we've been seeing some very weird lag on multi-core
	// systems and this seems to correct it..
	if (! SetProcessAffinityMask( GetCurrentProcess(), 1 ) )
		TRACE( "SetProcessAffinityMask failed...!" );
	// initialize the localized text system, this will force the locale.txt file
	// to be loaded from disk.
	LocalizedString::locale();

#ifdef _DEBUG
	Settings settings( "ClientD" );
#else
	Settings settings( "Client" );
#endif

	// watch for failures to allocate memory
	//MemoryGuard::initialize();

	CharString sCommandLine = m_lpCmdLine;
	CommandLine commands( sCommandLine );

	// pre-update options
	int nOption = -1;
	if ( (nOption = commands.find( "-setup" )) >= 0 ||
		(nOption = commands.find( "setup" )) >= 0 )
	{
		//m_RunLobbyOnExit = false;
		Process::start( CLIENT_SETUP_EXE );
		return FALSE;
	}

	SetRegistryKey( _T("Palestar") );

	CProgressDlg progress;
	progress.Create();

	// LocalServer.exe will signal this event after it has started
	Event serverStarted( "serverStart" );

#ifdef _DEBUG
	progress.SetStatus( _T("Saving constants...") );

	// load the constants
	Settings constants( "Constants", ".\\Constants.ini" );
	for(int i=0;i<Constant::constantCount();i++)
	{
		Constant * pConstant = Constant::constant( i );
		ASSERT( pConstant );

		constants.put( pConstant->name(), 
			CharString().format("%f", pConstant->value()) );

		TRACE( CharString().format("%s = %f", pConstant->name(), pConstant->value()) );
	}
#else
	progress.SetStatus( _T("Loading constants...") );

	// load the constants
	Settings constants( "Constants", ".\\Constants.ini" );
	for(int i=0;i<Constant::constantCount();i++)
	{
		Constant * pConstant = Constant::constant( i );
		ASSERT( pConstant );

		CharString value( constants.get( pConstant->name(), "" ) );
		if ( value.length() > 0 )
			(*pConstant) = strtod( value, NULL );
	}
#endif

	progress.SetStatus( _T("Checking for DX8...") );

	// Verify that DirectX 8.0 is installed
	HINSTANCE hD3D8DLL = LoadLibrary( _T("D3D8.DLL") );
	if( hD3D8DLL == NULL )
	{
		MessageBox( NULL, _T("DirectX 8.0 is required to play DarkSpace!"), _T("DX8 not installed"), MB_OK );
		return FALSE;
	}
	// DX8 is installed
	FreeLibrary( hD3D8DLL );

	// make sure the settings are valid, run ClientSetup.exe if necessary
	if ( settings.get( "setup", (dword)0 ) != 3 )
	{
		progress.SetStatus( _T("Running setup...") );
		void * setup = Process::start( CLIENT_SETUP_EXE );
		if ( setup == NULL )
		{
			MessageBox( NULL, _T("Failed to run ClientSetup.exe!"), _T("Failed"), MB_OK );
			return 0;
		}

		if ( Process::wait( setup ) < 0 )
			return 0;		// user canceled setup, exit

		settings.put( "setup", 3 );
	}
#if !defined(_DEBUG)
	if ( settings.get( "exit", (dword)0 ) != 0 )
	{
		// program did not exit properly, display dialog so the user can submit a bug report
		Process::wait( Process::start( BUG_REPORT_EXE ) );
		settings.put( "exit", (dword)0 );
	}
#endif

	// log all trace messages...
	//GetTraceOutput().initialize( DebugOutput::RF_DEBUGGER | DebugOutput::RF_LOGFILE | DebugOutput::RF_RESET_LOGFILE, "Trace" );

	progress.SetStatus( _T("Opening data folder...") );
	// open the data files
	BrokerFolder theData;
	if (! theData.open( settings.get( "data", ".\\data\\" ), false, false ) )
	{
		MessageBox( NULL, _T("Failed to open the data directory!"), _T("Failed"), MB_OK );
		return 0;
	}

	// parse the command line options..
	int nLastOption = 0;
	for(int nArg=0;nArg<commands.argumentCount();)
	{
		const char * pArg = commands.argument( nArg );
		if ( stricmp( pArg, "-tutorial" ) == 0 || 
			stricmp( pArg, "tutorial" ) == 0 )				// depreciated command
		{
			CSelectTutorial dialog;
			if ( dialog.DoModal() != IDOK )
				return 0;

			m_sAddress = "localhost";
			m_nPort = 9001;

			commands.append( CommandLine( CharString().format("-run %s", dialog.m_Selected.string()) ) );
			nArg += 1;
		}
		else if ( stricmp( pArg, "-run" ) == 0 && commands.isValid( nArg + 1 ) )
		{
			m_sAddress = "localhost";
			m_nPort = 9001;

			progress.SetStatus( _T("Starting local server...") );

			void * hServer = Process::start( CharString().format("%s %s", LOCAL_SERVER_EXE, commands.argument(nArg + 1)) );
			// wait for server to startup
			while( serverStarted.wait( 1000 ) )
			{
				if ( !Process::active( hServer ) )
				{
					MessageBox( NULL, _T("Failed to start local server!"), _T("Failed"), MB_OK );
					return 0;
				}
			}
			nArg += 2;
		}
		else if ( stricmp( pArg, "-connect" ) == 0 && commands.isValid( nArg + 2 ) )
		{
			m_sAddress = commands.argument( nArg + 1);
			m_nPort = commands.argumentInt( nArg + 2 );
			nArg += 3;
		}
		else if ( stricmp( pArg, "-login" ) == 0 && commands.isValid( nArg + 2 ) )
		{
			m_SessionId = MetaClient::getSessionId( commands.argument( nArg + 1 ), 
				commands.argument( nArg + 2 ) );
			nArg += 3;
		}

		if ( nLastOption == nArg )
			break;			// no option was found, stop parsing for options...
		nLastOption = nArg;
	}

	// nLastOption is not really the last option, but the first argument after the options..
	// last arguments may be <address> <port> <session>
	int nStart = nLastOption;
	if ( commands.isValid( nStart + 2 ) )
	{
		m_sAddress = commands.argument( nStart );
		m_nPort = commands.argumentInt( nStart + 1 );
		m_SessionId = commands.argumentDWORD( nStart + 2 );
	}

	// add search paths
	FileDisk::resetSearchPaths();
	FileDisk::addSearchPath(".\\");

	progress.SetStatus( _T("Loading interface...") );
	// load the interface
	InterfaceContext::Link pInterface;
	if (! loadResource( settings.get( "interface", DEFAULT_INTERFACE ) , pInterface ) )
	{
		MessageBox( NULL, _T("Failed to load the interface!"), _T("Failed"), MB_OK );
		return 0;
	}

	progress.SetStatus( _T("Connecting to server...") );

	// get the GameClient object
	GameDocument * pDoc = WidgetCast<GameDocument>( pInterface->document() );
	if ( pDoc == NULL )
	{
		MessageBox( NULL, _T("Invalid document object!"), _T("Error"), MB_OK );
		return 0;
	}

	GameClient * pClient = pDoc->client();
	ASSERT( pClient );

	if (! pDoc->connect( m_SessionId, m_sAddress, m_nPort ) )
	{
		if ( pClient->serverVersion() != 0 && pClient->version() != pClient->serverVersion() )
		{
			MessageBox( NULL, _T("Invalid server version or server is full; please wait and try again later!"), _T("Failed"), MB_OK );
			return 0;
		}

		MessageBox( NULL, _T("Failed to connect to server; the server may be down or check your internet connection!"), _T("Failed"), MB_OK );
		return 0;
	}
	
	progress.SetStatus( _T("Initializing platform...") );
	// create the platform object
	Platform::Ref pPlatform = Platform::create();
	if (! pPlatform )
	{
		MessageBox( NULL, _T("Failed to initialize the platform!"), _T("Error"), MB_OK );
		return 0;
	}

	// initialize the platform object
	Platform::Context context;
	context.instance = AfxGetInstanceHandle();
	context.applicationName = CharString().format("DarkSpace: Universe v1.006", GameClient::sm_nVersion );
	context.displayMode = settings.get( "displayMode", "" );
	context.audioKhz = settings.get( "audioKhz", 44100 );
	context.audioChan = settings.get( "audioChan", 2 ); 
	context.audioVoices = settings.get( "audioVoices", 16 );
	context.audioBits = settings.get( "audioBits", 16 );

	PrimitiveMaterial::sm_bEnableSpecular = settings.get( "enableSpecular", 1 );
	PrimitiveSurface::sm_bEnableLightMaps = settings.get( "enableLightmap", 1 );
	PrimitiveSurface::sm_bEnableTextureCompression = settings.get( "enableTextureCompression", 1 ) != 0;
	DisplayDevice::sm_WaitVB = settings.get( "waitVB", 1 );
	Font::s_EnableAlpha = settings.get( "alphaFonts", 1 );
	
	Trail::s_TrailScalar = Clamp( float( settings.get( "shipTrails", 100 ) ) / 100.0f, 0.0f, 1.0f );
	NounProjectile::s_TrailScalar = Clamp( float( settings.get( "missileTrails", 100) ) / 100.0f, 0.0f, 1.0f );
	NodeParticleSystem::s_fDetail = Clamp( float( settings.get( "particleEffects", 100 ) ) / 100.0f, 0.0f, 1.0f );

	// set the texture detail
	CharString textureDetail = settings.get( "textureDetail", "HIGH" );
	if ( textureDetail == "LOW" )
		Material::sm_MaxTextureSize = SizeInt( 256, 256 );
	else if ( textureDetail == "MEDIUM" )
		Material::sm_MaxTextureSize = SizeInt( 512, 512 );
	else
		Material::sm_MaxTextureSize = SizeInt( 1024, 1024 );

	// set the graphics detail...
	NodeBoolean::s_DetailLevel = (NodeBoolean::DetailLevel)settings.get( "graphicsDetail", 1 );		// default to medium

	// get the FPS limit
	int nLimitFPS = settings.get( "FPSLimit", (dword)0 );
	pInterface->setFPSLimit( nLimitFPS );

	TRACE( CharString().format("Material::sm_MaxTextureSize = %d x %d", Material::sm_MaxTextureSize.width, Material::sm_MaxTextureSize.height ) );

	if ( context.displayMode.length() == 0 )
	{
		MessageBox( NULL, _T("Failed to load settings; please run ClientSetup before running the Client!"), _T("Invalid Settings"), MB_OK );
		return FALSE;
	}

	if (! pPlatform->initialize( context ) )
	{
		MessageBox( NULL, _T("Failed to initialize the platform; please run ClientSetup to check your client settings."), _T("Error"), MB_OK );
		return FALSE;
	}

	// set the sound effects volume
	if ( pPlatform->audio() )
	{
		float fVolume = Clamp<float>( settings.get( "sfxVolume", 75 ), 0.0f, 100.0f);
		pPlatform->audio()->setVolume(  fVolume / 100.0f );
	}

	// set the exit value to non-zero, so if we crash then the bug report will run next time
	settings.put( "exit", 1 );
	// close the progress dialog
	progress.DestroyWindow();
	
	// get our display device
	DisplayDevice::Ref pDevice = pPlatform->display();
	// set this to allow textures to background load..
	DisplayDevice::sm_pCacheDevice = pDevice;
	// run the game
	RunGame( pInterface );
	// lock our device until it's destroyed...
	pDevice->lock();
	// clean up created objects
	pInterface.reset();
	pPlatform = NULL;

	// uninstall our memory guard
	//MemoryGuard::release();
	// Flush all textures
	Material::flushSurfaceCache();

	if ( m_Error )
	{
		MessageBox( NULL, m_ErrorMessage, _T("Caught Exception"), MB_OK );
		return FALSE;
	}

	// clean exit, set the exit value to zero
	settings.put( "exit", (dword)0 );

	return 0;
}

//----------------------------------------------------------------------------

BOOL CClientApp::InitInstance()
{
	//AfxEnableControlContainer();

	return StartGame();
}

int CClientApp::ExitInstance() 
{
	// terminate any running local server
	Event serverEnd( "serverEnd" );
	serverEnd.signal();
	
	return CWinApp::ExitInstance();
}

//----------------------------------------------------------------------------
// EOF

